/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.superduperbudget.central.core;

import android.content.Context;
import com.liato.bankdroid.banking.Account;
import com.liato.bankdroid.banking.Bank;
import com.liato.bankdroid.banking.Transaction;
import com.liato.bankdroid.banking.exceptions.BankChoiceException;
import com.liato.bankdroid.banking.exceptions.BankException;
import com.liato.bankdroid.banking.exceptions.LoginException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import se.superduperbudget.central.compiler.CharSequenceCompiler;
import se.superduperbudget.central.db.SuperDuperAccount;
import se.superduperbudget.central.db.SuperDuperTransaction;
import se.superduperbudget.central.db.SuperDuperUserBank;

/**
 *
 * @author Emil
 */
public class Sync {
    public static boolean sync(SuperDuperUserBank userBank) {        
        boolean res = false;
        try {
            Bank bank = getBank(userBank);
            login(bank, userBank);
            get(bank);
            updateDb(bank, userBank);
            res = true;
        } catch (Exception ex) {
            Logger.getLogger(Sync.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return res;
    }

    private static Bank getBank(SuperDuperUserBank userBank) throws Exception {
        String code = userBank.getBank().getCode();
        
        code = code.replace("package", "//package");
        Matcher classMatcher = Pattern.compile("class(.*?)(extends|implements|\\{)").matcher(code);
        
        if (!classMatcher.find())
            throw new Exception("No class found in code");
        
        String name = classMatcher.group(1).trim();
        
        CharSequenceCompiler<Bank> compiler = new CharSequenceCompiler<Bank>(ClassLoader.getSystemClassLoader(), null);
        Class<Bank> bankClass = compiler.compile(name, code, null, Bank.class);
        return bankClass.getConstructor(Context.class).newInstance((Object) null);
    }

    private static void login(Bank bank, SuperDuperUserBank userBank) {
        bank.setUsername(userBank.getUsername());
        bank.setPassword(userBank.getPassword());
    }

    private static void get(Bank bank) throws BankException, LoginException, BankChoiceException {
        bank.update();
        bank.updateAllTransactions();
        bank.closeConnection();
    }

    private static void updateDb(Bank bank, SuperDuperUserBank userBank) {
        ArrayList<Account> realBankAccounts = bank.getAccounts();
        ArrayList<SuperDuperAccount> userBankAccounts = new ArrayList<SuperDuperAccount>(userBank.getAccounts());
        
        for (Account rba : realBankAccounts) {
            String rbaName = rba.getName();
            boolean found = false;
            for (int i = 0; i < userBankAccounts.size(); i++) {
                SuperDuperAccount sda = userBankAccounts.get(i);
                String sdaName = sda.getName();
                if (sdaName.equals(rbaName)) {
                    found = true;
                    userBankAccounts.remove(i);
                    updateAccount(sda, rba);
                    break;
                }
            }
            if (!found) {
                SuperDuperAccount sda = new SuperDuperAccount();
                userBank.getAccounts().add(sda);
                
                sda.setName(rba.getName());
                sda.setTransactions(new ArrayList<SuperDuperTransaction>());                
                updateAccount(sda, rba);
            }
        }
    }

    private static Date parseDate(String date) {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); // TODO: Parse other dates
        Date res = new Date();
        try {
            res = df.parse(date);
        } catch (ParseException ex) {
            Logger.getLogger(Sync.class.getName()).log(Level.SEVERE, null, ex);
        }   
        return res;
    }
    
    private static void updateAccount(SuperDuperAccount sda, Account rba) {
        sda.setBalance(rba.getBalance().floatValue());
        
        Collection<SuperDuperTransaction> sdaTrans = sda.getTransactions();
        ArrayList<SuperDuperTransaction> sortedTrans = new ArrayList<SuperDuperTransaction>(sda.getTransactions());
        
        Comparator<SuperDuperTransaction> descComp = new Comparator<SuperDuperTransaction>() {
            @Override
            public int compare(SuperDuperTransaction o1, SuperDuperTransaction o2) {
                return -o1.getDate().compareTo(o2.getDate());
            }
        };
        
        Collections.sort(sortedTrans, descComp); // Sort by date (DESC)
        
        for (Transaction rbt : rba.getTransactions()) {
            Date rbtDate = parseDate(rbt.getDate());
            SuperDuperTransaction sdt = null;
            for (SuperDuperTransaction sdtSearch : sortedTrans) {
                Date sdtDate = sdtSearch.getDate();
                if (sdtSearch.getDate().equals(rbtDate) && sdtSearch.getDiff() == rbt.getAmount().floatValue()) {   // TODO: == and floats?
                    sdt = sdtSearch;
                    break;
                }
                if (sdtDate.compareTo(rbtDate) < 0) {   // if passed the date, no need to search anymore
                    break;
                }
            }
            
            if (sdt == null) {
                sdt = new SuperDuperTransaction();
                sdaTrans.add(sdt);
            }
            
            sdt.setDiff(rbt.getAmount().floatValue());
            sdt.setText(rbt.getTransaction());
            sdt.setDate(rbtDate);
        }
    }
}
